#include "mesh.h"
#include "game.h"

#ifndef COM_RELEASE
#define COM_RELEASE(iface) if(iface) { iface->Release(); iface = NULL; }
#endif

/**
 * Konstruktor domyslny
 */
Mesh::Mesh() : Resource(), subsetCount(0), subsets(NULL), mesh(NULL)
{
}

/**
 * Destruktor
 */
Mesh::~Mesh()
{
	unload();
}

/**
 * Renderuje siatke
 *
 * @param device Urzadzenie Direct3D
 */
void Mesh::render(LPDIRECT3DDEVICE9 device) const
{
	for(int i = 0; i < subsetCount; i++)
	{
		const Texture* tex = subsets[i].getTexture();
		if(tex)
		{
			subsets[i].apply(device);
			int passCount = tex->beginDrawing();

			for(int pass = 0; pass < passCount; pass++)
			{
				tex->beginPass(pass);

				mesh->DrawSubset(i);

				tex->endPass();
			}

			tex->endDrawing();
		}
		else
		{
			subsets[i].apply(device);
			mesh->DrawSubset(i);
		}
	}
}

/**
 * Laduje siatke bezposrednio z danych pliku X.
 */
bool Mesh::loadFromXof(LPD3DXFILEDATA xofFileData)
{
	/*
		Info:
			Kod bardzo podobny do kodu metody load()
			Nie chcialo mi sie dopisywac nowej metody
			parsujacej (moze kiedys sie ja zrobi).
	*/

	HRESULT hr;
	DWORD numMaterials;
	LPD3DXBUFFER materialBuffer;
	LPD3DXBUFFER adjacency;
	LPD3DXBUFFER effects;

	// zaladuj siatke
	if(FAILED(hr = D3DXLoadMeshFromXof(xofFileData,
			D3DXMESH_MANAGED, Game::getGraphics()->getDevice(), &adjacency,
			&materialBuffer, &effects, &numMaterials, &mesh)))
		return false;

	// wczytaj materialy
	subsetCount = numMaterials;
	if(numMaterials)
	{
		subsets = new Subset[subsetCount];
		D3DXMATERIAL* mats = (D3DXMATERIAL*)materialBuffer->GetBufferPointer();
		for(DWORD i = 0; i < numMaterials; i++)
		{
			D3DMATERIAL9 mat = mats[i].MatD3D;
			mat.Ambient = mat.Diffuse;
			Texture* tex = NULL;

			// zaladuj teksture
			if(mats[i].pTextureFilename)
			{
				std::string textureFileName = "textures/";
				textureFileName += std::string(mats[i].pTextureFilename);

				tex = (Texture*)Game::getResourceManager()->getResourceByName(textureFileName);
				if(!tex)
				{
					// zaladuj teksture

					// efekt?
					int tfnlen = textureFileName.length();
					std::string textureFileNameExt = textureFileName.substr(tfnlen-3);
					if(0 == stricmp(textureFileNameExt.c_str(), ".fx"))
					{
						tex = (Texture*)Game::getResourceManager()->addResource(textureFileName, Resource::FxTextureResource);
					}
					else
					{
						tex = (Texture*)Game::getResourceManager()->addResource(textureFileName, Resource::TextureResource);
					}
				}

				if(!tex)
				{
					OutputDebugString("Nie mozna zaladowac tekstury '");
					OutputDebugString(textureFileName.c_str());
					OutputDebugString("'\n");
				}

			}

			subsets[i].setMaterial(mat);
			subsets[i].setTexture(tex);
			
			if(tex)
				tex->release();
		}
	}

	// optymalizuj siatke
	if(FAILED(mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT,
		(LPDWORD)adjacency->GetBufferPointer(), NULL, NULL, NULL)))
	{
		return false;
	}

	COM_RELEASE(materialBuffer);
	COM_RELEASE(adjacency);
	COM_RELEASE(effects);
	return genTriangleList();
}

/**
 * Laduje siatke z zasobu.
 * Scala wszystkie siatki w jedna.
 */
bool Mesh::load()
{
	HRESULT hr;
	DWORD numMaterials;
	LPD3DXBUFFER materialBuffer;
	LPD3DXBUFFER adjacency;

	// zaladuj siatke
	if(FAILED(hr = D3DXLoadMeshFromXInMemory(getData(), getDataSize(),
			D3DXMESH_MANAGED, Game::getGraphics()->getDevice(), &adjacency,
			&materialBuffer, NULL, &numMaterials, &mesh)))
		return false;

	// wczytaj materialy
	subsetCount = numMaterials;
	if(numMaterials)
	{
		subsets = new Subset[subsetCount];
		D3DXMATERIAL* mats = (D3DXMATERIAL*)materialBuffer->GetBufferPointer();
		for(DWORD i = 0; i < numMaterials; i++)
		{
			D3DMATERIAL9 mat = mats[i].MatD3D;
			mat.Ambient = mat.Diffuse;
			Texture* tex = NULL;

			// zaladuj teksture
			if(mats[i].pTextureFilename)
			{
				std::string textureFileName = "textures/";
				textureFileName += std::string(mats[i].pTextureFilename);
				tex = (Texture*)Game::getResourceManager()->getResourceByName(textureFileName);
				if(!tex)
					tex = (Texture*)Game::getResourceManager()->addResource(textureFileName, Resource::TextureResource);
				if(!tex)
				{
					OutputDebugString("Nie mozna zaladowac tekstury '");
					OutputDebugString(textureFileName.c_str());
					OutputDebugString("'\n");
				}
			}

			subsets[i].setMaterial(mat);
			subsets[i].setTexture(tex);
			
			if(tex)
				tex->release();
		}
	}

	// optymalizuj siatke
	if(FAILED(mesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT,
		(LPDWORD)adjacency->GetBufferPointer(), NULL, NULL, NULL)))
	{
		return false;
	}

	COM_RELEASE(materialBuffer);
	COM_RELEASE(adjacency);

	return genTriangleList();
}

/**
 * ...
 */
void Mesh::unload()
{
	if(subsets)
	{
		for(int i = 0; i < subsetCount; i++)
			subsets[i].setTexture(NULL);
		delete[] subsets;
		subsets = NULL;
		subsetCount = 0;
	}
	if(mesh)
	{
		mesh->Release();
		mesh = NULL;
	}
}

/**
 * Generuje liste trojkatow na podstawie siatki.
 */
bool Mesh::genTriangleList()
{
	Triangle *tri;
	LPD3DXMESH newMesh = NULL;
	D3DVECTOR* vertices = NULL;
	WORD* indices;

	if(!mesh)
		return false;
	if(FAILED(mesh->CloneMeshFVF(D3DXMESH_MANAGED, D3DFVF_XYZ, Game::getGraphics()->getDevice(), &newMesh)))
		return false;
	if(FAILED(newMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&vertices)))
		return false;
	if(FAILED(newMesh->LockIndexBuffer(D3DLOCK_READONLY, (LPVOID*)&indices)))
		return false;

	triangles.clear();
	boundingBox = BoundingBox();
	DWORD faces = newMesh->GetNumFaces();
	for(DWORD f = 0; f < faces; f++)
	{
		tri = new Triangle(
			Vector(vertices[indices[3*f]]),
			Vector(vertices[indices[3*f+1]]),
			Vector(vertices[indices[3*f+2]]));
		//tri->recalculatePlane();
		//tri->recalculateBoundingSphere();
		tri->recalculateBoundingBox();
		triangles.push_back(tri);
		boundingBox.addInternalBox(tri->getBoundingBox());
	}
	newMesh->UnlockVertexBuffer();
	newMesh->UnlockIndexBuffer();
	newMesh->Release();
	return true;
}

/**
 * Ustawia teksture danej podsiatki
 */
void Mesh::setSubsetTexture(int subsetId, Texture* texture)
{
	if(subsetId < subsetCount)
		subsets[subsetId].setTexture(texture);
}
